import { GameMapItemType, MonsterHp, MonsterAttack, WeaponAddAttack, ItemAddHp } from './GameDefine';
import Signal from "../engine/Signal";
import GameMapModel from "./model/GameMapModel";
import GameBagModel from "./model/GameBagModel";
import GameRoleModel from "./model/GameRoleModel";
import AStarTool from '../tool/AStarTool';

export enum GameRoleCtrl{
    NONE,
    LEFT,
    UP,
    RIGHT,
    DOWN
}

export enum GameEvent{
    GAME_START,
    GAME_END,
    MAP_ENTER,
    MAP_EXIT,
    BAG_UPDATE,
    MAP_UPDATE,
    KEY_NOT_ENOUGH,
    ROLE_MOVE,
    ROLE_ATTACK,
    ROLE_DEAD,
    ROLE_UPDATE,
    MONSTER_ATTACK,
    MONSTER_DEAD,
}

export default class GameCtrl {
    private static _instance: GameCtrl = null;
    public static getInstance(): GameCtrl {
        if(this._instance == null){
            this._instance = new GameCtrl();
        }
        return this._instance;
    }

    public signal: Signal<(ev: GameEvent, ...param)=>void> = new Signal();

    private _gameBag: GameBagModel = null;
    private _gameMap: GameMapModel = null;
    private _gameRole: GameRoleModel = null;

    private _astar: AStarTool = null;

    private _curMapId: number = 1;
    private _originAtk: number = 0;
    private _originHp: number = 0;
    private _isEnd = true;

    public init(): void{
        this._gameBag = GameBagModel.getInstance();
        this._gameMap = GameMapModel.getInstance();
        this._gameRole = GameRoleModel.getInstance();
        this._isEnd = true;
        this._astar = new AStarTool();
        this._astar.init([GameMapItemType.WALL]);
    }

    public startGame(isRestart: boolean){
        this._gameBag.init();
        this._gameRole.init(isRestart ? this._originHp : 5, isRestart ? this._originAtk : 5);

        this._isEnd = false;
        this.signal.trigger(GameEvent.GAME_START);

        this.enterMap(isRestart ? this._curMapId : 1);
    }

    private endGame(){
        this._isEnd = true;

        let isWin = this._gameRole.prop.curHp > 0;
        this.signal.trigger(GameEvent.GAME_END, isWin);
    }

    private enterMap(mapId: number){
        this._curMapId = mapId;
        this._originHp = this._gameRole.prop.maxHp;
        this._originAtk = this._gameRole.prop.atk;

        this._gameMap.init(mapId);
        this._gameRole.curPos.set(this._gameMap.getRoleInitPos(mapId));

        this.signal.trigger(GameEvent.MAP_ENTER);
    }

    private exitMap(){
        this.signal.trigger(GameEvent.MAP_EXIT);
    }

    public getPathTo(pos: cc.Vec2){
        if(this._isEnd) return null;
        let curPos = this._gameRole.curPos;
        let path = this._astar.getPath(this._gameMap.data, curPos, pos);

        return path;
    }
    
    public roleMoveTo(targetPos: cc.Vec2){
        if(this._isEnd) return false;

        return this._doRoleMove(targetPos);
    }

    public roleMove(ctl: GameRoleCtrl){
        if(this._isEnd) return false;
        if(ctl == GameRoleCtrl.NONE) return false;

        let targetPos = this._gameRole.curPos.clone();
        switch(ctl){
            case GameRoleCtrl.LEFT: {
                targetPos.y -= 1;
            }
                break;
            case GameRoleCtrl.RIGHT: {
                targetPos.y += 1;
            }
                break;
                
            case GameRoleCtrl.UP: {
                targetPos.x -= 1;
            }
                break;
            case GameRoleCtrl.DOWN: {
                targetPos.x += 1;
            }
                break;
        }
        
        this._doRoleMove(targetPos);
    }

    private _doRoleMove(targetPos: cc.Vec2): boolean{
        let dir = cc.v2(targetPos.y - this._gameRole.curPos.y, -(targetPos.x - this._gameRole.curPos.x));
        let mapData = this._gameMap.data;
        let bagData = this._gameBag.data;
        let roleData = this._gameRole.prop;

        if(targetPos.x < 0 || targetPos.x >= mapData.length || targetPos.y < 0 || targetPos.y >= mapData[targetPos.x].length){
            return false;
        }

        let item = mapData[targetPos.x][targetPos.y];
        let needMove = false;
        let bagUpdate = false;
        let mapUpdate = false;
        let roleUpdate = false;
        let nextMap = false;

        switch(item){
            case GameMapItemType.NONE: {
                needMove = true;
            }
                break;

            case GameMapItemType.WALL: {
                needMove = false;
            }
                break;
                
            case GameMapItemType.EXIT: {
                needMove = true;
                nextMap = true;
            }
                break;

                
            case GameMapItemType.DOOR_H:
            case GameMapItemType.DOOR_V: {
                let cnt = bagData.keys[GameMapItemType.KEY_1];
                if(cnt > 0){
                    bagData.keys[GameMapItemType.KEY_1]--;
                    bagUpdate = true;

                    mapData[targetPos.x][targetPos.y] = GameMapItemType.DOOR_OPEN;
                    mapUpdate = true;
                }else{
                    this.signal.trigger(GameEvent.KEY_NOT_ENOUGH, GameMapItemType.KEY_1);
                }
            }
                break;
                
            case GameMapItemType.DOOR_OPEN: {
                needMove = true;
            }
                break;

            case GameMapItemType.WEAPON_1:
            case GameMapItemType.WEAPON_2:
            case GameMapItemType.WEAPON_3:
            case GameMapItemType.WEAPON_4:
            case GameMapItemType.ITEM_1:
            case GameMapItemType.ITEM_2:
            case GameMapItemType.ITEM_3:
            case GameMapItemType.ITEM_4:
            case GameMapItemType.ITEM_5: {
                if(WeaponAddAttack[item] != null){
                    //加攻击
                    let addAtk = WeaponAddAttack[item];
                    
                    roleData.atk += addAtk;
                    roleUpdate = true;
                }
                
                if(ItemAddHp[item] != null){
                    //加血
                    let addHp = ItemAddHp[item];

                    roleData.curHp = Math.min(roleData.maxHp, roleData.curHp + addHp);
                    roleUpdate = true;
                }
                
                mapData[targetPos.x][targetPos.y] = GameMapItemType.NONE;
                mapUpdate = true;

                needMove = true;
            }
                break;
                
            case GameMapItemType.KEY_1:
            case GameMapItemType.KEY_2:{
                bagData.keys[item]++;
                bagUpdate = true;
                
                mapData[targetPos.x][targetPos.y] = GameMapItemType.NONE;
                mapUpdate = true;

                needMove = true;
            }
                break;

            case GameMapItemType.MONSTER_1:
            case GameMapItemType.MONSTER_2:
            case GameMapItemType.MONSTER_3:
            case GameMapItemType.MONSTER_4:
            case GameMapItemType.MONSTER_5:
            case GameMapItemType.MONSTER_6: 
            case GameMapItemType.MONSTER_7: 
            case GameMapItemType.MONSTER_8: 
            case GameMapItemType.MONSTER_9: 
            case GameMapItemType.BOSS_1:
            case GameMapItemType.BOSS_RANGE: {
                if(item == GameMapItemType.BOSS_RANGE){
                    item = GameMapItemType.BOSS_1;
                    targetPos = this._gameMap.getBossPos();
                }
                let monsterHp = MonsterHp[item];
                let monsterOriginHp = monsterHp;
                let monsterAtk = MonsterAttack[item];

                //玩家攻击
                monsterHp -= roleData.atk;
                this.signal.trigger(GameEvent.ROLE_ATTACK, dir, targetPos);

                if(monsterHp <= 0){
                    //怪物死亡
                    this.signal.trigger(GameEvent.MONSTER_DEAD, targetPos);

                    mapData[targetPos.x][targetPos.y] = GameMapItemType.NONE;
                    if(item == GameMapItemType.BOSS_1){
                        for(let ii = 0; ii < mapData.length; ii++){
                            for(let jj = 0; jj < mapData[ii].length; jj++){
                                if(mapData[ii][jj] == GameMapItemType.BOSS_RANGE){
                                    mapData[ii][jj] = GameMapItemType.NONE;
                                }
                            }
                        }
                    }
                    mapUpdate = true;

                    //增加攻击力和血量
                    let addAtk = monsterAtk;
                    let addHp = monsterOriginHp;
                    roleData.maxHp += addHp;
                    roleData.curHp += addHp;
                    roleData.atk += addAtk;
                    roleUpdate = true;
                }else{
                    //怪物没死，怪物攻击
                    roleData.curHp -= monsterAtk;
                    this.signal.trigger(GameEvent.MONSTER_ATTACK, targetPos, dir.neg(), this._gameRole.curPos);

                    if(roleData.curHp <= 0){
                        //玩家死亡
                        this.signal.trigger(GameEvent.ROLE_DEAD, this._gameRole.curPos);
                    }
                }
            }
                break;
        }
        
        if(needMove){
            this._gameRole.curPos.set(targetPos);
            this.signal.trigger(GameEvent.ROLE_MOVE, dir);
        }

        if(bagUpdate){
            this.signal.trigger(GameEvent.BAG_UPDATE);
        }

        if(mapUpdate){
            this.signal.trigger(GameEvent.MAP_UPDATE);
        }

        if(roleUpdate){
            this.signal.trigger(GameEvent.ROLE_UPDATE);
        }

        if(nextMap){
            //下一层
            if(this._curMapId == 2){
                this.endGame();
            }else{
                this.exitMap();
                this.enterMap(this._curMapId + 1);
            }
        }

        if(roleData.curHp <= 0){
            //玩家死亡
            this.endGame();
        }

        return needMove;
    }
}